import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * 对象与垃圾回收
 * Java的垃圾回收是Java语言的重要功能之一。
 * 当程序创建对象、数组等引用类型实体时，系统都会在堆内存中为之分配一块内存区，对象就保存在这块内存区中，
 * 当这块内存不再被任何引用变量引用时，这块内存就变成垃圾，等待垃圾回收机制进行回收。
 * <p>
 * 垃圾回收机制具有如下特征。
 * ➢ 垃圾回收机制只负责回收堆内存中的对象，不会回收任何物理资源（例如数据库连接、网络IO等资源）。
 * ➢ 程序无法精确控制垃圾回收的运行，垃圾回收会在合适的时候进行。当对象永久性地失去引用后，系统就会在合适的时候回收它所占的内存。
 * ➢ 在垃圾回收机制回收任何对象之前，总会先调用它的finalize()方法，该方法可能使该对象重新复活（让一个引用变量重新引用该对象），从而导致垃圾回收机制取消回收。
 * <p>
 * 当一个对象在堆内存中运行时，根据它被引用变量所引用的状态，可以把它所处的状态分成如下三种。
 * ➢ 可达状态：当一个对象被创建后，若有一个以上的引用变量引用它，则这个对象在程序中处于可达状态，
 * 程序可通过引用变量来调用该对象的实例变量和方法。
 * ➢ 可恢复状态：如果程序中某个对象不再有任何引用变量引用它，它就进入了可恢复状态。
 * 在这种状态下，系统的垃圾回收机制准备回收该对象所占用的内存，在回收该对象之前，
 * 系统会调用所有可恢复状态对象的finalize()方法进行资源清理。
 * 如果系统在调用finalize()方法时重新让一个引用变量引用该对象，则这个对象会再次变为可达状态；否则该对象将进入不可达状态。
 * ➢ 不可达状态：当对象与所有引用变量的关联都被切断，且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态，
 * 那么这个对象将永久性地失去引用，最后变成不可达状态。只有当一个对象处于不可达状态时，系统才会真正回收该对象所占有的资源。
 */
class StatusTranfer {
    static void test() {
        String a = new String("Java垃圾回收");  // "Java垃圾回收"处于可达状态
        a = new String("Java对象与垃圾回收");  // "Java垃圾回收"处于可恢复状态，"Java对象与垃圾回收"处于可达状态
    }
}

/**
 * 强制垃圾回收
 * 当一个对象失去引用后，系统何时调用它的finalize()方法对它进行资源清理，
 * 何时它会变成不可达状态，系统何时回收它所占有的内存，对于程序完全透明。
 * 程序只能控制一个对象何时不再被任何引用变量引用，绝不能控制它何时被回收。
 * 程序无法精确控制Java垃圾回收的时机，但依然可以强制系统进行垃圾回收—这种强制只是通知系统进行垃圾回收，
 * 但系统是否进行垃圾回收依然不确定。
 * 大部分时候，程序强制系统垃圾回收后总会有一些效果。
 * <p>
 * 强制系统垃圾回收有如下两种方式。
 * ➢ 调用System类的gc()静态方法：System.gc()。
 * ➢ 调用Runtime对象的gc()实例方法：Runtime.getRuntime().gc()。
 * <p>
 * 下面程序创建了4个匿名对象，每个对象创建之后立即进入可恢复状态，等待系统回收，但直到程序退出，系统依然不会回收该资源。
 */
class GcTest {
    @Override
    protected void finalize() {
        System.out.println("系统正在清理GcTest对象的资源...");
    }

    static void test() {
        for (int i = 0; i < 4; i++) {
            new GcTest();
        }
    }
}

/**
 * 编译、运行上面程序，看不到任何输出，可见直到系统退出，系统都不曾调用GcTest对象的finalize()方法。
 * 但如果将程序修改成如下形式
 * <p>
 * 提示：由于系统何时调用对象的finalize()方法是不确定的，因此从Java 9开始，该方法被标记为不推荐。
 */
class GcTest2 {
    @Override
    protected void finalize() {
        System.out.println("系统正在清理GcTest对象的资源...");
    }

    static void test() {
        for (int i = 0; i < 4; i++) {
            new GcTest2();
            // 下面两行代码的作用完全相同，强制系统进行垃圾回收
//            System.gc();
            Runtime.getRuntime().gc();
        }
    }
}

/**
 * 运行java命令时指定-verbose：gc选项，可以看到每次垃圾回收后的提示信息
 * <p>
 * 上面程序显示了强制垃圾回收的效果，但这种强制只是建议系统立即进行垃圾回收，系统完全有可能并不立即进行垃圾回收，
 * 垃圾回收机制也不会对程序的建议完全置之不理：垃圾回收机制会在收到通知后，尽快进行垃圾回收。
 */

/**
 * finalize方法
 * 在垃圾回收机制回收某个对象所占用的内存之前，通常要求程序调用适当的方法来清理资源，
 * 在没有明确指定清理资源的情况下，Java提供了默认机制来清理该对象的资源，这个机制就是finalize()方法。
 * 该方法是定义在Object类里的实例方法，方法原型为：
 * protected void finalize() throws Throwable
 * 当finalize()方法返回后，对象消失，垃圾回收机制开始执行。方法原型中的throws Throwable表示它可以抛出任何类型的异常。
 * <p>
 * 任何Java类都可以重写Object类的finalize()方法，在该方法中清理该对象占用的资源。
 * 如果程序终止之前始终没有进行垃圾回收，则不会调用失去引用对象的finalize()方法来清理资源。
 * 垃圾回收机制何时调用对象的finalize()方法是完全透明的，只有当程序认为需要更多的额外内存时，垃圾回收机制才会进行垃圾回收。
 * 因此，完全有可能出现这样一种情形：某个失去引用的对象只占用了少量内存，而且系统没有产生严重的内存需求，
 * 因此垃圾回收机制并没有试图回收该对象所占用的资源，所以该对象的finalize()方法也不会得到调用。
 * <p>
 * finalize()方法具有如下4个特点。
 * ➢ 永远不要主动调用某个对象的finalize()方法，该方法应交给垃圾回收机制调用。
 * ➢ finalize()方法何时被调用，是否被调用具有不确定性，不要把finalize()方法当成一定会被执行的方法。
 * ➢ 当JVM执行可恢复对象的finalize()方法时，可能使该对象或系统中其他对象重新变成可达状态。
 * ➢ 当JVM执行finalize()方法时出现异常时，垃圾回收机制不会报告异常，程序继续执行。
 * <p>
 * 下面程序演示了如何在finalize()方法里复活自身，并可通过该程序看出垃圾回收的不确定性。
 */
class FinalizeTest {
    private static FinalizeTest ft = null;

    void info() {
        System.out.println("测试资源清理的finalize方法");
    }

    static void test() {
        // 创建FinalizeTest对象立即进入可恢复状态
        new FinalizeTest();
        // 通知系统进行资源回收
        System.gc();
        // 强制系统立即调用可恢复对象的finalize()方法
        // 如果仅仅只调用System.gc();不调用下面的，
        // 由于JVM垃圾回收机制的不确定性，JVM往往并不立即调用可恢复对象的finalize()方法，
        // 这样FinalizeTest的ft类变量可能依然为null，可能依然会导致空指针异常。
        System.runFinalization();
        ft.info();
        // 如果没有调用 System.gc(); 则上一行会抛出空指针异常
    }

    @Override
    protected void finalize() {
        // 让ft引用到试图回收的可恢复对象，即可恢复对象重新变成可达
        ft = this;
    }
}

/**
 * 对象的软、弱和虚引用
 * <p>
 * 对大部分对象而言，程序里会有一个引用变量引用该对象，这是最常见的引用方式。
 * 除此之外，java.lang.ref包下提供了三个类：SoftReference、PhantomReference和WeakReference，
 * 它们分别代表了系统对对象的三种引用方式：软引用、虚引用和弱引用。
 * 因此，Java语言对对象的引用有如下4种方式。
 * 1.强引用（StrongReference）这是Java程序中最常见的引用方式。
 * 程序创建一个对象，并把这个对象赋给一个引用变量，程序通过该引用变量来操作实际的对象，前面介绍的对象和数组都采用了这种强引用的方式。
 * 当一个对象被一个或一个以上的引用变量所引用时，它处于可达状态，不可能被系统垃圾回收机制回收。
 * 2.软引用（SoftReference）软引用需要通过SoftReference类来实现，当一个对象只有软引用时，它有可能被垃圾回收机制回收。
 * 对于只有软引用的对象而言，当系统内存空间足够时，它不会被系统回收，程序也可使用该对象；
 * 当系统内存空间不足时，系统可能会回收它。软引用通常用于对内存敏感的程序中。
 * 3.弱引用（WeakReference）弱引用通过WeakReference类实现，弱引用和软引用很像，但弱引用的引用级别更低。
 * 对于只有弱引用的对象而言，当系统垃圾回收机制运行时，不管系统内存是否足够，总会回收该对象所占用的内存。
 * 当然，并不是说当一个对象只有弱引用时，它就会立即被回收—正如那些失去引用的对象一样，必须等到系统垃圾回收机制运行时才会被回收。
 * 4.虚引用（PhantomReference）虚引用通过PhantomReference类实现，虚引用完全类似于没有引用。
 * 虚引用对对象本身没有太大影响，对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时，那么它和没有引用的效果大致相同。
 * 虚引用主要用于跟踪对象被垃圾回收的状态，虚引用不能单独使用，虚引用必须和引用队列（ReferenceQueue）联合使用。
 * 上面三个引用类都包含了一个get()方法，用于获取被它们所引用的对象。
 * <p>
 * 引用队列由java.lang.ref.ReferenceQueue类表示，它用于保存被回收后对象的引用。
 * 当联合使用软引用、弱引用和引用队列时，系统在回收被引用的对象之后，将把被回收对象对应的引用添加到关联的引用队列中。
 * 与软引用和弱引用不同的是，虚引用在对象被释放之前，将把它对应的虚引用添加到它关联的引用队列中，这使得可以在对象被回收之前采取行动。
 * <p>
 * 软引用和弱引用可以单独使用，但虚引用不能单独使用，单独使用虚引用没有太大的意义。
 * 虚引用的主要作用就是跟踪对象被垃圾回收的状态，程序可以通过检查与虚引用关联的引用队列中是否已经包含了该虚引用，从而了解虚引用所引用的对象是否即将被回收。
 * <p>
 * 面程序示范了弱引用所引用的对象被系统垃圾回收过程。
 */
class ReferenceTest {
    static void test() {
        // 这里不要使用 String str = "Java四种引用方式";
        String str = new String("Java四种引用方式");
        // 创建一个弱引用，让此弱引用引用到"Java四种引用方式"字符串
        WeakReference wr = new WeakReference(str);
        // 切断str引用和"Java四种引用方式"字符串直接的引用
        str = null;
        // 取出弱引用所引用的对象
        System.out.println(wr.get());
        // 强制垃圾回收
        System.gc();
        System.runFinalization();
        // 再次取出弱引用所引用的对象，输出null
        System.out.println(wr.get());
    }
}

class PhantomReferenceTest {
    public static void test() {
        String str = new String("Java虚引用");
        // 创建一个引用队列
        ReferenceQueue rq = new ReferenceQueue();
        // 创建一个虚引用，让此虚引用引用到"Java虚引用"字符串
        PhantomReference pr = new PhantomReference(str, rq);
        // 切断str引用和"Java虚引用"字符串之间的引用
        str = null;
        // 取出虚引用所引用的对象，并不能通过虚引用获取被引用的对象，所以此处输出null
        System.out.println(pr.get());
        // 强制垃圾回收
        System.gc();
        System.runFinalization();
        // 垃圾回收之后，虚引用将被放入引用队列中
        // 取出引用队列中最进入队列的引用与pr进行比较
        System.out.println(rq.poll() == pr);
    }
}

/**
 * 使用这些引用类可以避免在程序执行期间将对象留在内存中。如果以软引用、弱引用或虚引用的方式引用对象，垃圾回收器就能够随意地释放对象。
 * 如果希望尽可能减小程序在其生命周期中所占用的内存大小时，这些引用类就很有用处。
 * <p>
 * 必须指出：要使用这些特殊的引用类，就不能保留对对象的强引用；如果保留了对对象的强引用，就会浪费这些引用类所提供的任何好处。
 * <p>
 * 由于垃圾回收的不确定性，当程序希望从软、弱引用中取出被引用对象时，可能这个被引用对象已经被释放了。
 * 如果程序需要使用那个被引用的对象，则必须重新创建该对象。
 * 这个过程可以采用两种方式完成
 */
class WeakReferenceTest {
    private static void test1() {
        String str = new String("Java四种引用方式");
        WeakReference wr = new WeakReference(str);
        str = null;

        // 方式1
        // 取出弱引用所引用的对象
        Object obj = wr.get();
        if (obj == null) {
            wr = new WeakReference(new String("Java四种引用方式"));   // ①
            obj = wr.get(); // ②
        }
        // 操作obj对象
        // 再次切断obj和对象直接的关联
        obj = null;
    }

    private static void test2() {
        String str = new String("Java四种引用方式");
        WeakReference wr = new WeakReference(str);
        str = null;

        // 方式2
        // 取出弱引用所引用的对象
        Object obj = wr.get();
        if (obj == null) {
            obj = new String("Java四种引用方式");
            wr = new WeakReference(obj);
        }
        // 操作obj对象
        // 再次切断obj和对象直接的关联
        obj = null;
    }

    /**
     * 但第一段代码存在一定的问题：当if块执行完成后，obj还是有可能为null。
     * 因为垃圾回收的不确定性，假设系统在①号和②号代码之间进行垃圾回收，则系统会再次将wr所引用的对象回收，从而导致obj依然为null。
     * 第二段代码则不会存在这个问题，当if块执行结束后，obj一定不为null。
     */
    static void test() {
        test1();
        test2();
    }
}

public class Test {
    public static void main(String[] args) {
        StatusTranfer.test();
        GcTest.test();
        GcTest2.test();
        FinalizeTest.test();
        ReferenceTest.test();
        PhantomReferenceTest.test();
        WeakReferenceTest.test();
    }
}
